use std::marker::PhantomData;

use nom::bytes::streaming::tag;
use nom::branch::alt;
use nom::combinator::opt;
use nom::sequence::preceded;

use crate::error::{CResult, ChunkIoError};
use super::error::ConfigError;
use crate::recipe::VersionFrom;

use super::StrValue;
use super::{Parse};
use nom::IResult;

pub trait Execute<T> {
    fn execute(&mut self,t:&T) -> CResult<String>;
}

#[derive(Debug)]
pub enum BackupStatementArg<'a> {
    Output(bool),
    Opt(&'a str,&'a str),
    StrValue(&'a str),
    None,
}


impl<'a> Parse<'a> for BackupStatementArg<'a> {
    fn parse(raw_str:&'a str) -> IResult<&'a str,Self> {
        let raw_str = raw_str.trim_start();
        if raw_str == "" { return Ok((raw_str,Self::None)) }
        let (raw_str,opt) = opt(alt((
            tag("-o"),
            tag("-t"),
            tag("-v"),
            tag("-w")
        )))(raw_str)?;
        if opt == Some("-o") {
            return Ok((raw_str,Self::Output(false)));
        } else if opt == Some("-t") {
            return Ok((raw_str,Self::Output(true)));
        }
        let raw_str = raw_str.trim_start();
        let (raw_str,value) = StrValue::parse(raw_str)?;
        if let Some(opt) = opt {
            Ok((raw_str,Self::Opt(opt,value.inner())))
        } else {
            Ok((raw_str,Self::StrValue(value.inner())))
        }
    }
}
 
#[derive(Debug)]
pub struct BackupStatement<'a> {
    path:Option<&'a str>,
    version:&'a str,
    working_directory:Option<&'a str>,
    output:Option<bool>,
}

impl<'a> BackupStatement<'a> {
    pub fn path(&self) -> CResult<&'a str> {
        self.path.map_or(Err(ChunkIoError::Config(ConfigError::BackupPathMissing)), |path|Ok(path))
    }

    pub fn version(&self) -> &'a str {
        self.version
    }

    pub fn working_directory(&self) -> Option<&'a str> {
        self.working_directory
    }

    pub fn output(&self) -> Option<bool> {
        self.output
    }
}

impl<'a> Parse<'a> for BackupStatement<'a> {
    fn parse(raw_str:&'a str) -> IResult<&'a str,Self> {
        let raw_str = raw_str.trim_start();
        let (raw_str,_) = alt((
            tag("backup"),
            tag("-b"),
            tag("--backup"),
        ))(raw_str)?;
        let mut time_output = None;
        let mut path = None;
        let mut version = None;
        let mut work_directory = None;
        let mut raw_str = raw_str;
        loop {
            let (new_raw_str,arg) = BackupStatementArg::parse(raw_str)?;
            match arg {
                BackupStatementArg::Output(is_table) => {
                    time_output = Some(is_table);
                }
                BackupStatementArg::Opt(opt,value) => {
                    match opt {
                        "-v" => {
                            version = Some(value);
                        }
                        "-w" => {
                            work_directory = Some(value);
                        }
                        _ => {
                            panic!("invalid option {}",opt);
                        }
                    }
                },
                BackupStatementArg::StrValue(value) => {
                    if let Some(_) = path {
                        break;
                    } else {
                        path = Some(value);
                    }
                }
                BackupStatementArg::None => {
                    break;
                }
            }
            raw_str = new_raw_str;
        }

        Ok((raw_str,Self {
            output:time_output,
            path:path,
            version:version.unwrap_or(&raw_str[0..0]),
            working_directory:work_directory,
        })) 
    }
}

#[derive(Debug)]
pub enum RestoreStatementArg<'a> {
    Output(bool),
    Version(VersionFrom),
    Opt(&'a str,&'a str),
    StrValue(&'a str),
    None,
}


impl<'a> Parse<'a> for RestoreStatementArg<'a> {
    fn parse(raw_str:&'a str) -> IResult<&'a str,Self> {
        let raw_str = raw_str.trim_start();
        if raw_str == "" { return Ok((raw_str,Self::None)) }
        let (raw_str,opt) = opt(alt((
            tag("-o"),
            tag("-t"),
            tag("-v"),
            tag("-w")
        )))(raw_str)?;
        if opt == Some("-o") {
            return Ok((raw_str,Self::Output(false)));
        } else if opt == Some("-t") {
            return Ok((raw_str,Self::Output(true)));
        }
        let raw_str = raw_str.trim_start();
        let (raw_str,value) = StrValue::parse(raw_str)?;
        if let Some(opt) = opt {
            if opt == "-v" {
                let version = if value.quotion() {
                    VersionFrom::Name(value.inner().to_string())
                } else {
                    let (rest,num) = nom::character::complete::u64(value.inner())?;
                    if rest != "" {
                        return Err(nom::Err::Failure(nom::error::Error::new(value.inner(), nom::error::ErrorKind::Digit)))
                    }
                    VersionFrom::Num(num)
                };
                Ok((raw_str,Self::Version(version)))
            } else {
                Ok((raw_str,Self::Opt(opt,value.inner())))
            }
        } else {
            Ok((raw_str,Self::StrValue(value.inner())))
        }
    }
}
 

#[derive(Debug)]
pub struct RestoreStatement<'a> {
    output:Option<bool>,
    path:Option<&'a str>,
    version:Option<VersionFrom>,
    working_directory:Option<&'a str>,
}

impl<'a> RestoreStatement<'a> {
    pub fn path(&self) -> CResult<&'a str> {
        self.path.map_or(Err(ChunkIoError::Config(ConfigError::RestorePathMissing)), |path|Ok(path))
    }

    pub fn version(&self) -> Option<VersionFrom> {
        self.version.clone()
    }

    pub fn output(&self) -> Option<bool> {
        self.output
    }

    pub fn working_directory(&self) -> Option<&'a str> {
        self.working_directory
    }
}

impl<'a> Parse<'a> for RestoreStatement<'a> {
    fn parse(raw_str:&'a str) -> IResult<&'a str,Self> {
        let raw_str = raw_str.trim_start();
        let (raw_str,_) = alt((
            tag("restore"),
            tag("-r"),
            tag("--restore"),
        ))(raw_str)?;
        
        let mut output = None;
        let mut path = None;
        let mut version = None;
        let mut work_directory = None;
        let mut raw_str = raw_str;
        loop {
            let (new_raw_str,arg) = RestoreStatementArg::parse(raw_str)?;
            match arg {
                RestoreStatementArg::Output(is_table) => {
                    output = Some(is_table);
                }
                RestoreStatementArg::Version(version_from) => {
                    version = Some(version_from);
                }
                RestoreStatementArg::Opt(opt,value) => {
                    match opt {
                        "-w" => {
                            work_directory = Some(value);
                        }
                        _ => {
                            panic!("invalid option {}",opt);
                        }
                    }
                },
                RestoreStatementArg::StrValue(value) => {
                    if let Some(_) = path {
                        break;
                    } else {
                        path = Some(value);
                    }
                }
                RestoreStatementArg::None => {
                    break;
                }
            }
            raw_str = new_raw_str;
        }

        Ok((raw_str,Self {
            path:path,
            version:version,
            output:output,
            working_directory:work_directory,
        })) 
    }
}

#[derive(Debug)]
pub enum VersionStatementArg {
    All,
    Find(VersionFrom),
}


impl<'a> Parse<'a> for VersionStatementArg {
    fn parse(raw_str:&'a str) -> IResult<&'a str,Self> {
        let raw_str = raw_str.trim_start();
        let (raw_str,opt) = opt(alt((
            tag("-a"),
            tag("-f")
        )))(raw_str)?;
        let raw_str = raw_str.trim_start();
        if let Some(opt) = opt {
            if opt == "-f" {
                let (raw_str,value) = StrValue::parse(raw_str)?;
                let version = if value.quotion() {
                    VersionFrom::Name(value.inner().to_string())
                } else {
                    let (rest,num) = nom::character::complete::u64(value.inner())?;
                    if rest != "" {
                        return Err(nom::Err::Failure(nom::error::Error::new(value.inner(), nom::error::ErrorKind::Digit)))
                    }
                    VersionFrom::Num(num)
                };
                Ok((raw_str,Self::Find(version)))
            } else if opt == "-a" {
                Ok((raw_str,Self::All))
            } else {
                unreachable!()
            }
        } else {
            Ok((raw_str,Self::All))
        }
    }
}
 
#[derive(Debug)]
pub struct VersionStatement<'a> {
    opt_arg:VersionStatementArg,
    working_directory:Option<&'a str>,
}

impl<'a> VersionStatement<'a> {
    pub fn opt_arg(&self)-> &VersionStatementArg {
        &self.opt_arg
    }

    pub fn working_directory(&self)-> Option<&'a str> {
        self.working_directory
    }
}

impl<'a> Parse<'a> for VersionStatement<'a> {
    fn parse(raw_str:&'a str) -> IResult<&'a str,Self> {
        let raw_str = raw_str.trim_start();
        let (raw_str,_) = alt((
            tag("version"),
            tag("--version"),
        ))(raw_str)?;
        let (raw_str,opt_arg) = VersionStatementArg::parse(raw_str)?;

        let raw_str = raw_str.trim_start();
        let (raw_str,value) = opt(preceded(tag("-w"), StrValue::parse))(raw_str)?;
        let working_directory = value.map(|v|v.inner());
        Ok((raw_str,Self {
            opt_arg:opt_arg,
            working_directory:working_directory,
        }))
    }
}

#[derive(Debug)]
pub struct Help<'a>(PhantomData<&'a str>);
pub const HELP_INFO:&str = r#"
I'm a help info.
"#;

impl<'a> Help<'a> {
    pub fn new() -> Self {
        Self(PhantomData)
    }
    pub fn execute(&self) -> String {
        format!("{}",HELP_INFO)
    }
}

impl<'a> Parse<'a> for Help<'a> {
    fn parse(raw_str:&'a str) -> IResult<&'a str,Self> {
        let raw_str = raw_str.trim_start();
        let (raw_str,_) = alt((
            tag("help"),
            tag("-h"),
            tag("--help"),
        ))(raw_str)?;
        Ok((raw_str,Help::new()))
    }
}